﻿using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using LinFu.Reflection.Emit;
using System.Reflection;

namespace CoreEx.Common.Extensions
{
    /// <summary>
    /// Extends the <see cref="CilWorker"/> class.
    /// </summary>
    public static class CilWorkerExtensions
    {
        /// <summary>
        /// Pushes the <paramref name="type"/> onto the stack
        /// </summary>
        /// <param name="cilWorker">The <see cref="CilWorker"/> that will be used to create the instructions.</param>
        /// <param name="moduleDefinition">The module that contains the host method.</param>
        /// <param name="methodDefinition">The target method.</param>
        /// <param name="type">The <see cref="Type"/> to push onto the stack.</param>
        public static void PushType(this CilWorker cilWorker,ModuleDefinition moduleDefinition,MethodDefinition methodDefinition, Type type)
        {            
            //Create a variable that will reference the type
            VariableDefinition typeVariable = methodDefinition.AddLocal(typeof(Type));
            
            //Import the type
            TypeReference typeReference = moduleDefinition.ImportType(type);
            
            //Get a reference to the Type.GetTypeFromHandle method that will be used to retrieve the actual type
            MethodReference getTypeFromHandle = moduleDefinition.ImportMethod<Type>("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static);            
            cilWorker.Emit(OpCodes.Ldtoken, typeReference);
            cilWorker.Emit(OpCodes.Call, getTypeFromHandle);
            cilWorker.Emit(OpCodes.Stloc, typeVariable);           

            //Load the type onto the stack
            cilWorker.Emit(OpCodes.Ldloc, typeVariable);
        }

        /// <summary>
        /// Pushes the <paramref name="types"/> onto the stack in the form of an array.
        /// </summary>
        /// <param name="cilWorker">The <see cref="CilWorker"/> that will be used to create the instructions.</param>
        /// <param name="moduleDefinition">The module that contains the host method.</param>
        /// <param name="methodDefinition">The target method.</param>
        /// <param name="types">The <see cref="Type"/> array to push onto the stack.</param>
        public static void PushTypes(this CilWorker cilWorker,ModuleDefinition moduleDefinition,MethodDefinition methodDefinition, Type[] types)
        {
            //Create a variable that will reference the new Type[] array
            VariableDefinition typeArray = methodDefinition.AddLocal(typeof (Type[]));

            //Define the array type
            TypeReference arrayType = moduleDefinition.ImportType(typeof (Type));                       

            //Create a new array to hold the types
            cilWorker.Emit(OpCodes.Ldc_I4, types.Length);
            cilWorker.Emit(OpCodes.Newarr, arrayType);
            cilWorker.Emit(OpCodes.Stloc, typeArray);            

            //Fill the array
            for (int i = 0; i < types.Length; i++)
            {
                cilWorker.Emit(OpCodes.Ldloc, typeArray);                
                cilWorker.Emit(OpCodes.Ldc_I4, i);
                cilWorker.PushType(moduleDefinition,methodDefinition,types[i]);
                cilWorker.Emit(OpCodes.Stelem_Ref);
            }

            //Load the array onto the stack
            cilWorker.Emit(OpCodes.Ldloc, typeArray);
        }

    }
}
